package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.promotion.CheckIssueStartMsgDTO;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.dto.*;
import com.tianji.promotion.entity.Coupon;
import com.tianji.promotion.entity.CouponScope;
import com.tianji.promotion.entity.UserCoupon;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.vo.CouponDetailVO;
import com.tianji.promotion.vo.CouponPageVO;
import com.tianji.promotion.vo.CouponScopeVO;
import com.tianji.promotion.vo.CouponVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author xjs
 */
@Slf4j
@Service
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @Autowired
    private ICouponScopeService scopeService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IExchangeCodeService codeService;

    @Autowired
    private CatalogueClient catalogueClient;

    @Autowired
    private RabbitMqHelper mqHelper;

    @Autowired
    private IUserCouponService userCouponService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCoupon(CouponFormDTO dto) {
        // 保存coupon
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        this.save(coupon);
        // 判断使用范围
        if(dto.getSpecific()!=null && dto.getSpecific()){
            // 如果是限定了范围
            // 限定到了哪些分类id上
            List<Long> categoryIds = dto.getScopes();
            // 保存到coupon_scope表
            List<CouponScope> scopes = new ArrayList<>();
            for (Long categoryId : categoryIds) {
                CouponScope scope = new CouponScope();
                scope.setType(1);
                scope.setCouponId(coupon.getId());
                scope.setBizId(categoryId);
                scopes.add(scope);
            }
            scopeService.saveBatch(scopes);
        }
    }

    @Override
    public PageDTO<CouponPageVO> pageQueryCoupon(CouponQuery query) {
        Integer status = query.getStatus();
        String name = query.getName();
        Integer type = query.getType();
        // 1.分页查询
        Page<Coupon> page = lambdaQuery()
                .eq(type != null, Coupon::getDiscountType, type)
                .eq(status != null, Coupon::getStatus, status)
                .like(StringUtils.isNotBlank(name), Coupon::getName, name)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        // 2.处理VO
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        List<CouponPageVO> list = BeanUtils.copyList(records, CouponPageVO.class);
        // 3.返回
        return PageDTO.of(page, list);
    }

    @Override
    public void issue(Long id, CouponIssueFormDTO dto) {
        // 根据id查询coupon
        Coupon couponDB = this.getById(id);
        if(couponDB == null){
            throw new BizIllegalException("查询不到优惠券");
        }
        // 判断状态
        Integer statusDB = couponDB.getStatus();
        CouponStatus couponStatus = CouponStatus.of(statusDB);
        if(couponStatus != CouponStatus.DRAFT && couponStatus != CouponStatus.PAUSE){
            throw new BizIllegalException("优惠券状态错误");
        }
        // dto转entity
        Coupon updateCoupon = BeanUtils.copyBean(dto, Coupon.class);
        updateCoupon.setId(id);
        if(dto.getIssueBeginTime() == null){
            // 立即发放
            updateCoupon.setIssueBeginTime(LocalDateTime.now());
            updateCoupon.setStatus(CouponStatus.ISSUING.getValue());
            //TODO 发送检查到期时间的延迟消息
        }else{
            // 如果开始时间在当前时间之前，也是发放中
            if(dto.getIssueBeginTime().isBefore(LocalDateTime.now())){
                updateCoupon.setStatus(CouponStatus.ISSUING.getValue());
                updateCoupon.setIssueBeginTime(LocalDateTime.now());
                //TODO 发送检查到期时间的延迟消息
            }else{
                updateCoupon.setStatus(CouponStatus.UN_ISSUE.getValue());
                // 发送检查开始时间的延迟消息，如果开始时间到了，修改状态为发放中
                // 开始发放时间
                LocalDateTime issueBeginTime = dto.getIssueBeginTime();
                // 当前时间
                LocalDateTime now = LocalDateTime.now();
                // 延迟时间
                Duration delay = DateUtils.between(now, issueBeginTime);
                // 消息
                CheckIssueStartMsgDTO msg = new CheckIssueStartMsgDTO();
                msg.setCouponId(id);
                // 发送延迟消息
                log.info("发送延迟消息：msg：{}， delay：{}", msg, delay);
                mqHelper.sendDelayMessage(MqConstants.Exchange.PROMOTION_DELAY_EXCHANGE,
                        MqConstants.Key.PROMOTION_DELAY_KEY,
                        msg,
                        delay);
            }
        }
        // 判断是否是兑换码
        Integer obtainWay = couponDB.getObtainWay();
        ObtainType obtainType = ObtainType.of(obtainWay);
        if(obtainType == ObtainType.ISSUE && couponStatus != CouponStatus.PAUSE){
            // 生成兑换码
            // 优惠券的数量 = 兑换码的数量
            Integer totalNum = couponDB.getTotalNum();
            // 生成兑换码
            codeService.asyncGenerateExchageCode(totalNum, id, updateCoupon.getIssueEndTime());
        }
        // 更新到数据库
        this.updateById(updateCoupon);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCoupon(Long id, CouponFormDTO dto) {
        Coupon couponDB = this.getById(id);
        AssertUtils.isNotNull(couponDB, "优惠券不存在");
        CouponStatus status = CouponStatus.of(couponDB.getStatus());
        if (status != CouponStatus.DRAFT) {
            throw new BizIllegalException("只能修改未发放的优惠券");
        }
        Coupon couponUpdate = BeanUtils.copyBean(dto, Coupon.class);
        couponUpdate.setId(id);
        this.updateById(couponUpdate);
        // 判断使用范围
        if (couponDB.getSpecific() != null && couponDB.getSpecific()) {
            // 如果是限定了范围，删除旧的范围
            scopeService.deleteByCouponId(id);
        }
        if(dto.getSpecific() != null && dto.getSpecific()){
            List<Long> categoryIds = dto.getScopes();
            // 保存到coupon_scope表
            List<CouponScope> scopes = new ArrayList<>();
            for (Long categoryId : categoryIds) {
                CouponScope scope = new CouponScope();
                scope.setType(1);
                scope.setCouponId(id);
                scope.setBizId(categoryId);
                scopes.add(scope);
            }
            scopeService.saveBatch(scopes);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteCoupon(Long id) {
        Coupon couponDB = this.getById(id);
        AssertUtils.isNotNull(couponDB, "优惠券不存在");
        CouponStatus status = CouponStatus.of(couponDB.getStatus());
        if (status != CouponStatus.DRAFT) {
            throw new BizIllegalException("只能修改未发放的优惠券");
        }
        // 删除优惠券
        this.removeById(id);
        // 删除范围
        Boolean specific = couponDB.getSpecific();
        if(specific != null && specific){
            scopeService.deleteByCouponId(id);
        }
        // 删除兑换码
        Integer obtainWay = couponDB.getObtainWay();
        ObtainType obtainType = ObtainType.of(obtainWay);
        if(obtainType == ObtainType.ISSUE){
            codeService.deleteByCouponId(id);
        }
    }

    @Override
    public CouponDetailVO getCoupon(Long id) {
        Coupon couponDB = this.getById(id);
        AssertUtils.isNotNull(couponDB, "优惠券不存在");
        CouponDetailVO vo = BeanUtils.copyBean(couponDB, CouponDetailVO.class);
        List<CouponScope> scopes = scopeService.getByCouponId(id);
        if(!CollUtils.isEmpty(scopes)){
            List<CouponScopeVO> scopeVoList = BeanUtils.copyList(scopes, CouponScopeVO.class);
            List<Long> categoryIds = scopeVoList.stream().map(v -> v.getId()).collect(Collectors.toList());
            List<CataSimpleInfoDTO> catalogList = catalogueClient.batchQueryCatalogue(categoryIds);
            Map<Long, String> catalogMap = catalogList.stream().collect(Collectors.toMap(CataSimpleInfoDTO::getId, v -> v.getName()));
            scopeVoList.stream().forEach(v->v.setName(catalogMap.get(v.getId())));
            vo.setScopes(scopeVoList);
        }
        return vo;
    }

    @Override
    public void pause(Long id) {
        Coupon couponDB = this.getById(id);
        AssertUtils.isNotNull(couponDB, "优惠券不存在");
        CouponStatus status = CouponStatus.of(couponDB.getStatus());
        if (status != CouponStatus.ISSUING) {
            throw new BizIllegalException("只能暂停发放中的优惠券");
        }
        Coupon couponUpdate = new Coupon();
        couponUpdate.setId(id);
        couponUpdate.setStatus(CouponStatus.PAUSE.getValue());
        couponUpdate.setUpdateTime(LocalDateTime.now());
        this.updateById(couponUpdate);
    }

    @Override
    public void startIssue(CheckIssueStartMsgDTO dto) {
        Long id = dto.getCouponId();
        Coupon couponDB = this.getById(id);
        AssertUtils.isNotNull(couponDB, "优惠券不存在");
        CouponStatus status = CouponStatus.of(couponDB.getStatus());
        if (status != CouponStatus.UN_ISSUE) {
            throw new BizIllegalException("优惠券状态错误");
        }
        // 状态修改为发放中
        Coupon couponUpdate = new Coupon();
        couponUpdate.setId(id);
        couponUpdate.setStatus(CouponStatus.ISSUING.getValue());
        couponUpdate.setUpdateTime(LocalDateTime.now());
        this.updateById(couponUpdate);
    }

    @Override
    public List<CouponVO> listIssueing() {
        // 获取当前用户
        Long userId = UserContext.getUser();
        // 查询coupon列表： 状态是发放中 && 领取方式是手动领取
        LambdaQueryWrapper<Coupon> queryWrapper = new LambdaQueryWrapper<Coupon>();
        queryWrapper.eq(Coupon::getStatus, CouponStatus.ISSUING.getValue())
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC.getValue());
        List<Coupon> list = this.list(queryWrapper);
        if(CollectionUtils.isEmpty(list)){
            return new ArrayList<>();
        }

        List<CouponVO> voList = new ArrayList<>();
        // 遍历每一个优惠券，
        for (Coupon coupon : list) {
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);
            if(userId == null){
                vo.setReceived(false);
                vo.setAvailable(true);
            }else{
                // 查询用户的领券，包含了使用过的和未使用
                List<UserCoupon> userCoupons = userCouponService.getByUserIdCouponId(userId, coupon.getId());
                if(CollectionUtils.isEmpty(userCoupons)){
                    userCoupons = new ArrayList<>();
                }
                // 判断当前用户是否可领取
                //   查询用户领券的数量 是否是 小于 优惠券的限领数量 && 优惠券总的已经领取数量 < 优惠券的最大数量
                if(userCoupons.size() < coupon.getUserLimit() && coupon.getIssueNum() < coupon.getTotalNum()){
                    vo.setAvailable(true);
                }
                // 判断当用户是否可使用
                //   查询用户领券，判断未使用的数量，只要有一个是未使用，那就可以使用
                long unUsedCount = userCoupons.stream().filter(uc ->
                        Objects.equals(uc.getStatus(), UserCouponStatus.UNUSED.getValue())).count();
                if(unUsedCount > 0){
                    vo.setReceived(true);
                }
            }
            voList.add(vo);
        }
        // 组装结果集
        return voList;
    }

    @Override
    public int incrementIssueNum(Long id, Integer issueNum) {
        return this.baseMapper.incrementIssueNum(id, issueNum);
    }

    @Override
    public List<Coupon> getAvailableByUserId(Long userId) {
        int status = UserCouponStatus.UNUSED.getValue();
        return this.baseMapper.selectByUserIdStatus(userId, status);
    }
}
